﻿//===============================================================================
// Jammer.NET Development Library
// Jmr.Web.Silverlight
//===============================================================================
// Copyright © Junaid Fakhruddin, Matthew Leedham and Randolph Cabral.  
// All rights reserved.
//===============================================================================
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================

using System;
using System.Collections;
using System.IO;
using Jmr.Compression;
using Jmr.Entity.Poco;
using Jmr.Entity.Poco.Silverlight;
using Jmr.Serialization;
using Microsoft.Practices.EnterpriseLibrary.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Data;
using Microsoft.AnalysisServices.AdomdClient;
using System.Linq;
using System.Xml.Linq;
using System.Linq.Expressions;
using IQToolkit;
using System.Configuration;
using System.Collections.Generic;
using System.Linq.Dynamic;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Runtime.Serialization;


namespace Jmr.Web.Silverlight
{
	/// <summary>
	/// EntitySessionServicePageBase class.  Provides the service base implementation for the Silverlight EntitySessionProxy.
	/// </summary>
	public class EntitySessionServicePageBase : System.Web.UI.Page
	{
		private BinaryEntityFormatter _binaryformatter = null;

		/// <summary>
		/// EntitySessionServicePageBase constructor.
		/// </summary>
		public EntitySessionServicePageBase()
		{
			_binaryformatter = new BinaryEntityFormatter();
		}

		/// <summary>
		/// Overridden OnLoad event handler.
		/// </summary>
		/// <param name="e">Event args.</param>
		protected override void OnLoad(EventArgs e)
		{
			Response.Clear();
			Response.ExpiresAbsolute = DateTime.Now;
			Response.Buffer = true;

			try
			{
				var actionstring = Request.Headers[EntitySessionHeaderKeys.EntitySessionAction];
				if (string.IsNullOrEmpty(actionstring))
					throw new SafariHeaderBugException("Oops! Safari forgot to send the request headers! Boo on you SAFARI!!\n\nPlease use Internet Explorer, Firefox, or Google Chrome for a better experience.");
				
				var action = (Jmr.Entity.Poco.Silverlight.Action)Enum.Parse(typeof(Jmr.Entity.Poco.Silverlight.Action), Request.Headers[EntitySessionHeaderKeys.EntitySessionAction]);

				if (action == Jmr.Entity.Poco.Silverlight.Action.ExecuteQuery ||
						action == Jmr.Entity.Poco.Silverlight.Action.ExecuteMDXQuery)
				{
					ProcessDynamicDataQuery();
					return;
				}
				else if (action == Jmr.Entity.Poco.Silverlight.Action.FetchResults)
				{
					ProcessQueryExpression(); 
					return;
				}

				var mappingassemblyfullname = Request.Headers[EntitySessionHeaderKeys.MappingAssemblyFullName];
				var mappingfullname = Request.Headers[EntitySessionHeaderKeys.MappingFullName];
				var entityassemblyfullname = Request.Headers[EntitySessionHeaderKeys.EntityAssemblyFullName];
				var entityfullname = Request.Headers[EntitySessionHeaderKeys.EntityFullName];
				var entityargfullname = Request.Headers[EntitySessionHeaderKeys.EntityArgFullName];
        var entityArgLength = Request.InputStream.Length;
				var entityarg = new byte[entityArgLength];
				
				Request.InputStream.Seek(0,0);
				BinaryReader reader = new BinaryReader(Request.InputStream);
				while (true)
				{
					int bytesRead = reader.Read(entityarg, 0, entityarg.Length);
					if (bytesRead <= 0)
						break;
				}

        //var hashcode = Request.Headers["hashcode"];

				bool isSecurityAssembly = entityfullname.ToLower().Contains("jmr.security");
				entityassemblyfullname = (isSecurityAssembly) ? typeof(EntitySessionServicePageBase).Assembly.FullName : entityassemblyfullname;

				var entityType = ParseType(mappingassemblyfullname, entityfullname);
				var entityArgType = (entityargfullname == "NULL") ? null : (IsTypeofList(action))? ParseListType(mappingassemblyfullname, entityargfullname) : ParseType(mappingassemblyfullname, entityargfullname);
				var mappingType = ParseType(mappingassemblyfullname, mappingfullname);
				var entityArgInst = GetEntityArgInstance(entityArgType, entityarg);

				switch (action)
				{
					case Jmr.Entity.Poco.Silverlight.Action.FetchSet:
						FetchSet(mappingType, entityType, entityArgType, entityArgInst);
						break;
					case Jmr.Entity.Poco.Silverlight.Action.FetchSingle:
						FetchSingle(mappingType, entityType, entityArgType, entityArgInst);
						break;
					case Jmr.Entity.Poco.Silverlight.Action.Insert:
						Insert(mappingType, entityArgType, entityArgInst);
						break;
					case Jmr.Entity.Poco.Silverlight.Action.InsertBatch:
						InsertBatch(mappingType, entityArgType, entityArgInst);
						break;
					case Jmr.Entity.Poco.Silverlight.Action.Update:
						Update(mappingType, entityArgType, entityArgInst);
						break;
					case Jmr.Entity.Poco.Silverlight.Action.UpdateBatch:
						UpdateBatch(mappingType, entityArgType, entityArgInst);
						break;
					case Jmr.Entity.Poco.Silverlight.Action.Delete:
						Delete(mappingType, entityArgType, entityArgInst);
						break;
					case Jmr.Entity.Poco.Silverlight.Action.DeleteBatch:
						DeleteBatch(mappingType, entityArgType, entityArgInst);
						break;
				}
			}
			catch (Exception ex)
			{
				RaiseException(ex);
			}
			finally
			{
				Response.Flush();
				Response.End();
			}
		}

		private bool IsTypeofList(Jmr.Entity.Poco.Silverlight.Action action)
		{
			return (action == Jmr.Entity.Poco.Silverlight.Action.InsertBatch ||
				action == Jmr.Entity.Poco.Silverlight.Action.UpdateBatch ||
				action == Jmr.Entity.Poco.Silverlight.Action.DeleteBatch);
		}

		private void ProcessQueryExpression()
		{
			try
			{
				var action = (Jmr.Entity.Poco.Silverlight.Action)Enum.Parse(typeof(Jmr.Entity.Poco.Silverlight.Action), Request.Headers[EntitySessionHeaderKeys.EntitySessionAction]);
				var mappingassemblyfullname = Request.Headers[EntitySessionHeaderKeys.MappingAssemblyFullName];
				var mappingfullname = Request.Headers[EntitySessionHeaderKeys.MappingFullName];
				var entityassemblyfullname = Request.Headers[EntitySessionHeaderKeys.EntityAssemblyFullName];
				var entityfullname = Request.Headers[EntitySessionHeaderKeys.EntityFullName];
        var entityArgLength = Request.InputStream.Length;
				var entityarg = new byte[entityArgLength];

				Request.InputStream.Seek(0,0);
				BinaryReader reader = new BinaryReader(Request.InputStream);
				while (true)
				{
					int bytesRead = reader.Read(entityarg, 0, entityarg.Length);
					if (bytesRead <= 0)
						break;
				}

				var entityType = ParseType(mappingassemblyfullname, entityfullname);
				var mappingType = ParseType(mappingassemblyfullname, mappingfullname);

				DatabaseQueryExpression queryInfo = (DatabaseQueryExpression)GetEntityArgInstance(typeof(DatabaseQueryExpression), entityarg);

				switch (action)
				{
					case Jmr.Entity.Poco.Silverlight.Action.FetchResults:
						queryInfo.QueryXml = queryInfo.QueryXml.Replace("Jmr.Entity.Poco.Silverlight.Query", "IQToolkit.Query");
						XElement elem = XElement.Parse(queryInfo.QueryXml);
						FetchResults(elem, entityType, mappingType);
					break;
				}

			}
			catch (Exception e)
			{
				RaiseException(e);
			}
			finally
			{
				Response.Flush();
				Response.End();
			}
		}

		private void ProcessDynamicDataQuery()
		{
			try
			{
				var action = (Jmr.Entity.Poco.Silverlight.Action)Enum.Parse(typeof(Jmr.Entity.Poco.Silverlight.Action), Request.Headers[EntitySessionHeaderKeys.EntitySessionAction]);
        var entityArgLength = Request.InputStream.Length;
				var entityarg = new byte[entityArgLength];
				
				Request.InputStream.Seek(0,0);
				BinaryReader reader = new BinaryReader(Request.InputStream);
				while (true)
				{
					int bytesRead = reader.Read(entityarg, 0, entityarg.Length);
					if (bytesRead <= 0)
						break;
				}

				DatabaseQuery queryInfo = (DatabaseQuery)GetEntityArgInstance(typeof(DatabaseQuery), entityarg);
				switch (action)
				{
					case Jmr.Entity.Poco.Silverlight.Action.ExecuteQuery:
						ExecuteQuery(queryInfo);
						break;

					case Jmr.Entity.Poco.Silverlight.Action.ExecuteMDXQuery:
						ExecuteMDXQuery(queryInfo);
						break;
				}
			}
			catch (Exception ex)
			{
				RaiseException(ex);
			}
			finally
			{
				Response.Flush();
				Response.End();
			}
		}

		private void WriteResponse(MemoryStream stream)
		{
			var rawbytes = Utility.CompressHeaderless(stream).ToArray();
			var responseString = Convert.ToBase64String(rawbytes);
			var responseLength = responseString.Length;
			Response.Write(String.Format("{0}@@@{1}",responseLength, responseString));
		}

		private void RaiseException(Exception ex)
		{
			var serializedstream = _binaryformatter.Serialize(new JmrSerializationException
				{
					ExceptionType = ex.GetType().ToString(),
					Message = ex.Message,
					Source = ex.Source,
					StackTrace = ex.StackTrace,
					HelpLink = ex.HelpLink,
					InnerExceptionType = (ex.InnerException == null) ? null : ex.InnerException.GetType().ToString(),
					InnerExceptionMessage = (ex.InnerException == null) ? null : ex.InnerException.Message,
					InnerExceptionSource = (ex.InnerException == null) ? null : ex.InnerException.Source,
					InnerExceptionStackTrace = (ex.InnerException == null) ? null : ex.InnerException.StackTrace,
					InnerExceptionHelpLink = (ex.InnerException == null) ? null : ex.InnerException.HelpLink
				});

			WriteResponse(serializedstream);
		}

		private object GetEntityArgInstance(Type entityArgType, byte[] entityarg)
		{
			var retval = default(object);
			var entityargstream = (entityarg.Length == 0) ? null : new MemoryStream(entityarg);
			retval = (entityarg.Length == 0) ? null : _binaryformatter.Deserialize(entityArgType, entityargstream);
			return retval;
		}

		private void FetchSet(Type mappingType, Type entityType, Type entityArgType, object entityArg)
		{
			using (var sess = EntitySessionFactory.CreateSession(mappingType))
			{
				var entitylist = default(IEnumerable);
				try
				{
					entitylist = sess.FetchSet(entityType, entityArg);
				}
				catch (Exception ex)
				{
					RaiseException(ex);
					return;
				}
				finally
				{
					var serializedstream = _binaryformatter.Serialize(entitylist);
					WriteResponse(serializedstream);
				}
			}
		}

   
		private void FetchResults(XElement xmlExpression,Type entityType, Type mappingType)
		{
			using (var sess = EntitySessionFactory.CreateSession(mappingType))
			{
				var expression = default(Expression);
				IList results = default(IList);
				try
				{
					var DbMap = (Jmr.Entity.Poco.DatabaseMapper)Activator.CreateInstance(mappingType);
					var map = DbMap.GetMap(entityType);
					string connString = ConfigurationManager.ConnectionStrings[map.DatabaseName].ConnectionString;
					var provider = new JmrEntityProvider(entityType, connString, DbMap);
					var queryable = provider.GetTable(entityType, map.TableName).AsQueryable();

					ExpressionSerializer expSerializer = new ExpressionSerializer(DbMap);
					expression = expSerializer.Deserialize(xmlExpression);

					var queryresults = queryable.Provider.CreateQuery(expression);
					var resultType = typeof(List<>).MakeGenericType(entityType);
					results = (IList)Activator.CreateInstance(resultType);

					foreach (var item in queryresults)
						results.Add(item);

				}
				catch (Exception ex)
				{
					RaiseException(ex);
					return;
				}
				finally
				{
					var serializedstream = _binaryformatter.Serialize(results);
					WriteResponse(serializedstream);
				}
			}
		}

		private void ExecuteMDXQuery(DatabaseQuery queryInfo)
		{
			DynamicTable dynTable = new DynamicTable();
			try
			{
				string connString = String.Format("Provider=MSOLAP.1;Data Source={0};Initial Catalog={1};Persist Security Info=False;Integrated Security=SSPI;",
														queryInfo.ServerName,
														queryInfo.DatabaseName);

				AdomdConnection dbConn = null;
				dbConn = new AdomdConnection(connString);
				dbConn.Open();

				AdomdCommand cmd = new AdomdCommand(queryInfo.QueryString, dbConn);
				AdomdDataAdapter adapter = new AdomdDataAdapter(cmd);
				DataTable table = new DataTable();
				adapter.Fill(table);
				DynamicDataAdapter.Fill(table, dynTable);

				if (dbConn != null)
					dbConn.Close();
			}
			catch (Exception ex)
			{
				RaiseException(ex);
				return;
			}
			finally
			{
				var serializedstream = _binaryformatter.Serialize(dynTable, 99999);
				WriteResponse(serializedstream);

			}
		}

		private void ExecuteQuery(DatabaseQuery queryInfo)
		{
			DynamicTable dynTable = new DynamicTable();
			try
			{
				string connString = String.Format("Server={0};Initial Catalog={1};UID={3};PWD={4}",
					queryInfo.ServerName,
					queryInfo.DatabaseName,
					"False",
					queryInfo.UserId, 
					queryInfo.Password, 
					queryInfo.ProviderName);

				SqlConnection conn = new SqlConnection(connString);
				conn.Open();
				SqlCommand cmd = new SqlCommand(queryInfo.QueryString, conn);
				SqlDataAdapter adapter = new SqlDataAdapter(cmd);
				DataTable table = new DataTable();
				adapter.Fill(table);
				DynamicDataAdapter.Fill(table, dynTable);
				if (conn != null)
					conn.Close();
				
			}
			catch (Exception ex)
			{
				RaiseException(ex);
				return;
			}
			finally
			{
				//dynTable.Columns.Clear();
				//dynTable.Columns.Add("test");
				//dynTable.Rows.Clear();
				//var columns = new System.Collections.Generic.List<DynamicColumn> 
				//{ 
				//	new DynamicColumn { Name = "test" }
				//};

				//dynTable.Rows.Add(new DynamicRow
				//{
				//	Columns = columns, 
				//	Values = new System.Collections.Generic.List<DynamicColumnValue>{new DynamicColumnValue { Name = "test", Value = "text" }} 
				//});
				var serializedstream = _binaryformatter.Serialize(dynTable,99999);
				WriteResponse(serializedstream);
			}
		}

		private void FetchSingle(Type mappingType, Type entityType, Type entityArgType, object entityArg)
		{
			using (var sess = EntitySessionFactory.CreateSession(mappingType))
			{
				object entity = default(object);
				try
				{
					entity = sess.FetchSingle(entityType, entityArg);
				}
				catch (Exception ex)
				{
					RaiseException(ex);
					return;
				}
				finally
				{
					var serializedstream = _binaryformatter.Serialize(entity);
					WriteResponse(serializedstream);
				}
			}
		}

		private void Insert(Type mappingType, Type entityArgType, object entityArg)
		{
			using (var sess = EntitySessionFactory.CreateSession(mappingType))
			{
				//var recsAffected = sess.Insert(entityArg, false);
				//Response.Write(recsAffected.ToString());

				var retval = 0;
				try
				{
					retval = sess.Insert(entityArg, false);
				}
				catch (Exception ex)
				{
					RaiseException(ex);
					return;
				}
				finally
				{
					var serializedstream = _binaryformatter.Serialize(entityArg);
					WriteResponse(serializedstream);
				}
			}
		}

		private void Update(Type mappingType, Type entityArgType, object entityArg)
		{
			using (var sess = EntitySessionFactory.CreateSession(mappingType))
			{
				//var recsAffected = sess.Update(entityArg, false);
				//Response.Write(recsAffected.ToString());

				var retval = 0;
				try
				{
					retval = sess.Update(entityArg, false);
				}
				catch (Exception ex)
				{
					RaiseException(ex);
					return;
				}
				finally
				{
					var serializedstream = _binaryformatter.Serialize(entityArg);
					WriteResponse(serializedstream);
				}
			}
		}

		private void Delete(Type mappingType, Type entityArgType, object entityArg)
		{
			using (var sess = EntitySessionFactory.CreateSession(mappingType))
			{
				var recsAffected = sess.Delete(entityArg);
				Response.Write(recsAffected.ToString());
			}
		}

		private void InsertBatch(Type mappingType, Type entityType, object entityArg)
		{
			using (var sess = EntitySessionFactory.CreateSession(mappingType))
			{
				//var retval = 0;
				//retval = sess.InsertBatch(entityType, (IEnumerable)entityArg);
				//Response.Write(retval.ToString());


				var retval = 0;
				try
				{
					retval = sess.InsertBatch(entityType, (IEnumerable)entityArg);
				}
				catch (Exception ex)
				{
					RaiseException(ex);
					return;
				}
				finally
				{
					var serializedstream = _binaryformatter.Serialize(entityArg);
					WriteResponse(serializedstream);
				}
			}
		}

		private void UpdateBatch(Type mappingType, Type entityType, object entityArg)
		{
			using (var sess = EntitySessionFactory.CreateSession(mappingType))
			{
				//var retval = 0;
				//retval = sess.UpdateBatch(entityType, (IEnumerable)entityArg);
				//Response.Write(retval.ToString());

				var retval = 0;
				try
				{
					retval = sess.UpdateBatch(entityType, (IEnumerable)entityArg);
				}
				catch (Exception ex)
				{
					RaiseException(ex);
					return;
				}
				finally
				{
					var serializedstream = _binaryformatter.Serialize(entityArg);
					WriteResponse(serializedstream);
				}

			}
		}

		private void DeleteBatch(Type mappingType, Type entityType, object entityArg)
		{
			using (var sess = EntitySessionFactory.CreateSession(mappingType))
			{
				var retval = 0;
				retval = sess.DeleteBatch(entityType, (IEnumerable)entityArg);
				Response.Write(retval.ToString());
			}
		}

		private object GetInstance(string entityassemblyfullname, string entityfullname)
		{
			object retval = default(object);
			retval = Activator.CreateInstance(entityassemblyfullname, entityfullname).Unwrap();
			return retval;
		}

		private Type ParseType(string entityassemblyfullname, string entityfullname)
		{
			Type retval = default(Type);
			retval = GetInstance(entityassemblyfullname, entityfullname).GetType();
			return retval;
		}

		private Type ParseListType(string entityassemblyfullname, string entityfullname)
		{
			Type retval = default(Type);
			retval = typeof(List<>).MakeGenericType(GetInstance(entityassemblyfullname, entityfullname).GetType());
			return retval;
		}

	}

	/// <summary>
	/// SafariHeaderBugException class.
	/// </summary>
	public class SafariHeaderBugException : Exception
	{
		/// <summary>
		/// SafariHeaderBugException constructor.
		/// </summary>
		/// <param name="message">Error message.</param>
		public SafariHeaderBugException(string message) : base(message) { }
	}
}

